package com.jprocms.module.cms.dal.dataobject.content;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIncludeProperties;
import com.jpro.framework.common.util.collection.CollectionUtils;
import com.jpro.framework.tenant.core.db.TenantBaseDO;
import com.jprocms.module.cms.controller.admin.content.vo.ContentFileVo;
import com.jprocms.module.cms.controller.admin.content.vo.ContentImageVo;
import com.jprocms.module.cms.dal.dataobject.channel.ChannelDO;
import com.jprocms.module.cms.dal.dataobject.site.SiteDO;
import com.jprocms.module.cms.enums.EnumContentStatus;
import com.jprocms.module.cms.enums.EnumCreateMode;
import com.jprocms.module.cms.util.CmsContextUtil;
import com.jprocms.module.cms.util.FileUtil;
import com.jprocms.module.cms.util.ModelWrapper;
import com.jprocms.module.cms.web.IPageUrl;
import com.jprocms.module.cms.web.IUrl;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.commons.lang3.StringUtils;
import org.jsoup.Jsoup;
import org.springframework.lang.Nullable;

import javax.validation.constraints.Pattern;
import java.time.OffsetDateTime;
import java.util.*;
import java.util.stream.Collectors;

import static com.jpro.framework.common.util.string.StrUtils.replaceLatex;
import static com.jprocms.module.cms.constant.CmsSysConstants.*;
import static com.jprocms.module.cms.framework.util.StrUtils.formatDuration;
import static com.jprocms.module.cms.util.ModelWrapper.TYPE_EDITOR;
import static com.jprocms.module.cms.util.ModelWrapper.TYPE_FILE_UPLOAD;

/**
 * 内容 DO
 *
 * @author jprocms
 */

@TableName(value = "cms_content")
@KeySequence("cms_content_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@AllArgsConstructor
public class ContentDO extends TenantBaseDO implements IPageUrl, IUrl {
    /**
     * 内容的源栏目
     */
    public static final String REQUEST_ATTR_CONTENT_ORIGIN_CHANNEL_ID = "originChannelId";
    /**
     * 内容的源栏目MAP 批量移动的时候 内容id 作为key  源栏目id作为value
     */
    public static final String REQUEST_ATTR_CONTENT_ORIGIN_CHANNEL_ID_MAP = "originContentChannelIdMap";

    public static final String DOCUMENT_DOC = ".doc";
    public static final String DOCUMENT_DOCX = ".docx";
    public static final String DOCUMENT_PDF = ".pdf";

    public ContentDO() {
    }

    /**
     * ID
     */
    @TableId(value = "id", type = IdType.ASSIGN_ID)
    private Long id;
    /**
     * 模型ID
     */
    private Long modelId;
    /**
     * 站点ID
     */
    private Long siteId;
    /**
     * 栏目ID
     */
    private Long channelId;

    /**
     * 内容状态(1:已发布;   2:流转中;   3:待发布;  4:退回;  5:下线  6:草稿;   7 删除
     */
    private Integer status;
    /**
     * 内容标题
     */
    private String title;
    /**
     * 发布时间
     */
    private OffsetDateTime releaseTime;
    /**
     * 置顶级别   0非置顶 越大排序靠前
     */
    private Integer topLevel;
    /**
     * 原文ID
     */
    @TableField(updateStrategy = FieldStrategy.IGNORED)
    private Long originContentId;
    /**
     * 创建方式（1:直接创建    2:投稿  3:引用  4:复制
     */
    private Integer createMode;
    /**
     * 部门id
     */
    private Long deptId;
    /**
     * 创建人ID
     */
    private Long userId;
    /**
     * 标记属性
     */
    @TableField(updateStrategy = FieldStrategy.IGNORED)
    private Long dictMark;
    /**
     * 区块ID
     */
    @TableField(updateStrategy = FieldStrategy.IGNORED)
    private Long dictBlock;

    /**
     * 是否实时预览数据
     */
    private Boolean livePreview;
    /**
     * 原文内容
     */
    @Nullable
    @JsonIgnore
    @TableField(exist = false)
    private ContentDO originContent;

    @JsonIgnore
    @TableField(exist = false)
    private ContentExtDO ext = new ContentExtDO();

    @JsonIgnore
    @TableField(exist = false)
    private ContentCountDO count = new ContentCountDO();

    @JsonIgnore
    @TableField(exist = false)
    private ChannelDO channel = new ChannelDO();

    @JsonIgnore
    @TableField(exist = false)
    private SiteDO site = new SiteDO();

    /**
     * 引用列表
     */
    @TableField(exist = false)
    @JsonIncludeProperties({"id", "title", "channel", "site", "createMode"})
    private List<ContentDO> pushTos = Collections.emptyList();

    /**
     * tag关联表
     */
    @TableField(exist = false)
    private List<ContentTagDo> contentTagDos = new ArrayList<>();

    /**
     * 引用栏目关联表
     */
    @TableField(exist = false)
    private List<ContentSubChannelDO> subChannels = new ArrayList<>();

    /**
     * 自定义属性
     */
    @TableField(exist = false)
    private List<ContentAttrDO> attrDos = new ArrayList<>();
    /**
     * 自定义属性转换map结构
     */
    @Nullable
    @TableField(exist = false)
    private Map<String, Object> customs;

    public String getChannelName() {
        return getChannel().getName();
    }

    public String getSiteName() {
        return getSite().getName();
    }

    public Set<Long> getSubChannelIds() {
        final List<ContentSubChannelDO> subChannels = getSubChannels();
        if (CollUtil.isEmpty(subChannels)) {
            return new HashSet<>();
        }
        return CollectionUtils.convertSet(subChannels, ContentSubChannelDO::getChannelId);
    }

    public List<TagDO> getTags() {
        List<ContentTagDo> contentTagDos = getContentTagDos();
        //过滤逻辑删除
        contentTagDos = contentTagDos.stream().filter(t -> t != null && t.getTag() != null).collect(Collectors.toList());
        List<TagDO> tags = new ArrayList<>(contentTagDos.size());
        for (ContentTagDo tag : contentTagDos) {
            tags.add(tag.getTag());
        }
        return tags;
    }

    public List<String> getTagNameList() {
        if (CollUtil.isEmpty(getTags())) {
            return Collections.emptyList();
        }
        return getTags().stream().map(TagDO::getTagName).collect(Collectors.toList());
    }

    public List<Long> getTagIds() {
        if (CollUtil.isEmpty(getTags())) {
            return new ArrayList<>();
        }
        return getTags().stream().map(TagDO::getId).collect(Collectors.toList());
    }

    public String getTagNames() {
        return StrUtil.join(",", getTagNameList());
    }

    public Map<String, Object> getCustoms() {
        if (customs == null) {
            ModelWrapper modelWrapper = new ModelWrapper(getChannel().getContentModel());
            customs = modelWrapper.assembleCustoms(getAttrDos());
        }
        return customs;
    }

    public Map<String, Object> getCustomsWithoutEditor() {
        if (customs == null) {
            ModelWrapper modelWrapper = new ModelWrapper(getChannel().getContentModel());
            customs = modelWrapper.assembleCustoms(getAttrDos().stream().filter(c -> !TYPE_EDITOR.equals(c.getAttrType())).collect(Collectors.toList()));
        }
        return customs;
    }

    @Schema(description = "系统默认正文")
    @Nullable
    public String getText() {
        return getExt().getText();
    }

    @Schema(description = "系统默认正文带处理数学公式")
    @Nullable
    public String getLatexText() {
        return replaceLatex(getExt().getText());
    }


    @Schema(description = "纯文本格式的系统默认正文")
    public String getPlainText() {
        return Optional.ofNullable(getText())
                .map(html -> Jsoup.parse(html).body().text())
                // 多个空格替换为一个空格，中文空格unicode为 \u3000
                .map(text -> text.replaceAll("[ \\u3000]+", " "))
                .map(String::trim)
                .orElse(CharSequenceUtil.EMPTY);
    }

    public List<String> getTxts() {
        List<String> txts = new ArrayList<>();
        if (StringUtils.isNotBlank(getExt().getText())) {
            txts.add(getExt().getText());
        }
        if (CollUtil.isNotEmpty(getAttrDos())) {
            txts.addAll(CollectionUtils.convertList(getAttrDos().stream().filter(c -> TYPE_EDITOR.equals(c.getAttrType())).collect(Collectors.toList()), ContentAttrDO::getAttrValue));
        }
        return txts;
    }

    /**
     * 富文本内容map
     *
     * @return
     */
    public Map<String, Object> getCustomTxt() {
        if (CollUtil.isNotEmpty(getAttrDos())) {
            final List<ContentAttrDO> editors = getAttrDos().stream().filter(c -> TYPE_EDITOR.equals(c.getAttrType())).collect(Collectors.toList());
            if (CollUtil.isEmpty(editors)) {
                return new HashMap<>();
            }
            return CollectionUtils.convertMap(editors, ContentAttrDO::getName, ContentAttrDO::getAttrValue);
        }
        return new HashMap<>();
    }

    /**
     * 返回附件类型的url
     *
     * @return
     */
    public List<String> getAttachUrls() {
        List<String> attachUrls = new ArrayList<>();
        //系统默认字段-附件
        final List<ContentFileVo> attachs = getAttachs();
        if (CollUtil.isNotEmpty(attachs)) {
            for (ContentFileVo attach : attachs) {
                attachUrls.add(attach.getUrl());
            }
        }
        final String sysAttach = getSysAttach();
        if (StringUtils.isNotBlank(sysAttach)) {
            attachUrls.add(sysAttach);
        }
        //自定义字段-附件
        if (CollUtil.isNotEmpty(getAttrDos())) {
            attachUrls.addAll(CollectionUtils.convertList(getAttrDos().stream().filter(c -> TYPE_FILE_UPLOAD.equals(c.getAttrType())).collect(Collectors.toList()), ContentAttrDO::getAttrValue));
        }
        return attachUrls;
    }

    /**
     * 返回需要同步到es的 附件url 暂时处理 doc docx  pdf 三种类型
     *
     * @return
     */
    public List<String> getDocUrls() {
        List<String> attachUrls = getAttachUrls();
        if (CollUtil.isNotEmpty(attachUrls)) {
            List<String> filterUrls = new ArrayList<>();
            for (String attachUrl : attachUrls) {
                if (attachUrl.endsWith(DOCUMENT_DOC) || attachUrl.endsWith(DOCUMENT_DOCX) || attachUrl.endsWith(DOCUMENT_PDF)) {
                    filterUrls.add(attachUrl);
                }
            }
            return filterUrls;
        }
        return attachUrls;
    }


    @Schema(description = "是否可编辑")
    public boolean getEditable() {
        //已发布 流转中  归档 不能编辑修改且 引用内容不能修改
        if (EnumContentStatus.AUDITING.getStatus().equals(getStatus())) {
            return false;
        }
        if (EnumContentStatus.ARCHIVED.getStatus().equals(getStatus())) {
            return false;
        }
        if (getOriginContentId() != null) {
            return false;
        }
        return true;
    }

    @Schema(description = "是否已发布")
    public boolean isPublish() {
        if (super.getDeleted()) {
            return false;
        }
        return getStatus() == EnumContentStatus.PUBLISHED.getStatus();
    }

    @Schema(description = "跳转链接")
    @Nullable
    @Pattern(regexp = "^(http|/).*$")
    public String getLinkUrl() {
        if (getCreateMode() == EnumCreateMode.QUOTE.getMode() && getOriginContentId() != null) {
            return getOriginContent().getUrl();
        }
        return getExt().getOutLink();
    }

    @JsonIgnore
    @Nullable
    public String getStaticFile() {
        return getExt().getStaticFile();
    }

    public void setStaticFile(@Nullable String staticFile) {
        getExt().setStaticFile(staticFile);
    }

    @JsonIgnore
    @Nullable
    public String getH5StaticFile() {
        return getExt().getH5StaticFile();
    }

    public void setH5StaticFile(@Nullable String h5StaticFile) {
        getExt().setH5StaticFile(h5StaticFile);
    }


    @Override
    public String getName() {
        return getTitle();
    }

    @Schema(description = "URL地址")
    public String getUrl() {
        final String url = getUrl(1L);
        return url;
    }

    @Override
    public Boolean getTargetBlank() {
        return getExt().getTargetBlank();
    }

    public String getUrl(long page) {
        return getSite().getOpenSiteStaticPage() ? getStaticUrl(page) : getDynamicUrl(page);
    }

    @Schema(description = "动态URL地址")
    public String getDynamicUrl() {
        return getDynamicUrl(1);
    }

    @Schema(description = "URL地址")
    public String getPreviewUrl(String authorization) {
        if (StringUtils.isNotBlank(authorization)) {
            return getDynamicUrl(1L) + "?preview=true&Authorization=" + authorization;
        } else {
            return getDynamicUrl(1L) + "?preview=true";
        }
    }

    @Schema(description = "实时预览URL地址")
    public String getLivePreviewUrl(String authorization) {
        if (StringUtils.isNotBlank(authorization)) {
            return getDynamicUrl(1L) + "/live-preview?Authorization=" + authorization;
        } else {
            return getDynamicUrl(1L) + "/live-preview";
        }
    }

    public String getDynamicUrl(long page) {
        //有外部链接或者是引用的内容返回外部链接或原文url
        if (StringUtils.isNotBlank(getLinkUrl())) {
            return getLinkUrl();
        }
        StringBuilder buff = new StringBuilder(getSite().getDynamicUrl());
        String prefix = CONTENT_PREFIX;
        String contetPrefix = getSite().getGlobalConfigDO().getContentUrlPrefix();
        if (StringUtils.isNotBlank(contetPrefix)) {
            prefix = contetPrefix;
        }
        buff.append(prefix).append(SPT).append(getId());
        if (page > 1) {
            buff.append(SPT).append(page);
        }
        return buff.toString();
    }

    public String getStaticUrl(long page) {
        if (StringUtils.isNotBlank(getLinkUrl())) {
            return getLinkUrl();
        }
        return CmsContextUtil.isPc() ? getNormalStaticUrl(page) : getMobileStaticUrl(page);
    }

    public String getNormalStaticUrl(long page) {
        return getSite().getPcStaticUrl(getContentStaticFilePath(page));
    }

    public String getMobileStaticUrl(long page) {
        return getSite().getMobileStaticUrl(getContentStaticFilePath(page));
    }

    private String getContentStaticFilePath(long page) {
        return getSite().getContentFilePath(getChannelId(), getChannel().getPath(), getId(), getCreateTime(), page);
    }

    public String getPcStaticPath(long page) {
        return getSite().getPcStaticPath(getContentStaticFilePath(page));
    }

    public String getH5StaticPath(long page) {
        return getSite().getH5StaticPath(getContentStaticFilePath(page));
    }

    @JsonIgnore
    public String getTpl() {
        String template = CmsContextUtil.isPc() ? getExt().getPcTpl() : getExt().getH5Tpl();
        if (StringUtils.isBlank(template)) {
            template = getChannel().getContentTplAdapter();
            // 默认模板名为 content
            if (StringUtils.isBlank(template)) {
                template = TPL_CONTENT + SPT + "content";
            } else {
                template = TPL_CONTENT + SPT + template;
            }
        } else {
            template = TPL_CONTENT + SPT + template;
        }
        return getSite().joinTemplate(template);
    }

    public List<ContentFileVo> getAttachs() {
        return getExt().getAttachs();
    }

    public List<ContentImageVo> getImages() {
        return getExt().getImages();
    }

    @Schema(description = "视频时长。字符串格式，如：`20:15`")
    @Nullable
    public String getVideoDuration() {
        Integer duration = getExt().getSysVideoDuration();
        if (duration == null) {
            return null;
        }
        return formatDuration(duration);
    }

    public String getDocName() {
        return getExt().getDocName();
    }

    public String getDocPdf() {
        return getExt().getDocPdf();
    }

    public String getOriginDoc() {
        return getExt().getOriginDoc();
    }

    public String getSysAudio() {
        return getExt().getSysAudio();
    }

    public String getSysVideo() {
        return getExt().getSysVideo();
    }

    public String getSysImage() {
        return getExt().getSysImage();
    }

    public String getSource() {
        return getExt().getSource();
    }

    public String getAuthor() {
        return getExt().getAuthor();
    }

    public String getDescription() {
        return getExt().getDescription();
    }

    public String getKeywords() {
        return getExt().getKeywords();
    }

    public String getSubtitle() {
        return getExt().getSubtitle();
    }

    public String getSysAttach() {
        return getExt().getSysAttach();
    }

    public Integer getEditorType() {
        return getExt().getEditorType();
    }

    public Integer getViews() {
        return getCount().getViews();
    }

    public Integer getUps() {
        return getCount().getUps();
    }


    public Integer getDowns() {
        return getCount().getDowns();
    }

    public Integer getComments() {
        return getCount().getComments();
    }

    public Integer getFavorites() {
        return getCount().getFavorites();
    }

    public Integer getDownloads() {
        return getCount().getDownloads();
    }

    public String getSysAttachName() {
        return getExt().getSysAttachName();
    }

    public String getRejectReason() {
        return getExt().getRejectReason();
    }

    public String getProcessInstanceId() {
        return getExt().getProcessInstanceId();
    }

    public String getPcTpl() {
        return getExt().getPcTpl();
    }

    public String getH5Tpl() {
        return getExt().getH5Tpl();
    }

    public Date getTopStartTime() {
        return getExt().getTopStartTime();
    }

    public Date getOnlineTime() {
        return getExt().getOnlineTime();
    }

    public Date getOfflineTime() {
        return getExt().getOfflineTime();
    }

    public Date getTopEndTime() {
        return getExt().getTopEndTime();
    }

    public String getOutLink() {
        return getExt().getOutLink();
    }

    public Integer getSysVideoDuration() {
        return getExt().getSysVideoDuration();
    }


    @Schema(description = "文件尺寸。自动使用合适的单位，如 KB、MB 等")
    public String getSysAttachFileSizeStr() {
        return FileUtil.getSize(getSysAttachFileSize());
    }

    public Long getSysAttachFileSize() {
        return getExt().getSysAttachFileSize();
    }

    public void setSysAttachFileSize(@Nullable Long fileLength) {
        getExt().setSysAttachFileSize(fileLength);
    }

    /**
     * 必要参数初始化
     */
    public void init() {
        if (getReleaseTime() == null) {
            setReleaseTime(OffsetDateTime.now());
        }
        if (getSiteId() == null) {
            setSiteId(CmsContextUtil.getCurrSiteId());
        }
        if (getTopLevel() == null) {
            setTopLevel(STATUS_CLOSE);
        }
        if (getCreateMode() == null) {
            setCreateMode(EnumCreateMode.CREATE.getMode());
        }
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        if (!super.equals(o)) return false;
        ContentDO contentDO = (ContentDO) o;
        return Objects.equals(id, contentDO.id) && Objects.equals(modelId, contentDO.modelId) && Objects.equals(siteId, contentDO.siteId) && Objects.equals(channelId, contentDO.channelId) && Objects.equals(status, contentDO.status) && Objects.equals(title, contentDO.title) && Objects.equals(releaseTime, contentDO.releaseTime) && Objects.equals(topLevel, contentDO.topLevel) && Objects.equals(originContentId, contentDO.originContentId) && Objects.equals(createMode, contentDO.createMode) && Objects.equals(deptId, contentDO.deptId) && Objects.equals(userId, contentDO.userId) && Objects.equals(dictMark, contentDO.dictMark) && Objects.equals(dictBlock, contentDO.dictBlock) && Objects.equals(livePreview, contentDO.livePreview);
    }

    @Override
    public int hashCode() {
        return Objects.hash(super.hashCode(), id, modelId, siteId, channelId, status, title, releaseTime, topLevel, originContentId, createMode, deptId, userId, dictMark, dictBlock, livePreview);
    }

    @Override
    public String toString() {
        return "ContentDO{id=" + getId() + ", name=" + getName() + "}";
    }

}
